using System;
using System.Collections.Generic;
using System.Linq;

namespace Framework
{
    public static class EnumerableExtension
    {

        // SEE: OfType<>
        public static TResult As<TBase, TResult>(this IEnumerable<TBase> items) where TResult : TBase
        {
            return items.OfType<TResult>().FirstOrDefault();
        }
        public static List<TResult> AsList<TBase, TResult>(this IEnumerable<TBase> items) where TResult : TBase
        {
            return items.OfType<TResult>().ToList();
        }


        /// <summary>
        /// LINQ Distinct on a particular Property
        /// http://stackoverflow.com/questions/489258/linq-distinct-on-a-particular-property
        /// </summary>
        /// <typeparam name="TSource">The type of the source.</typeparam>
        /// <typeparam name="TKey">The type of the key.</typeparam>
        /// <param name="source">The source.</param>
        /// <param name="keySelector">The key selector.</param>
        /// <returns></returns>
        public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            var seenKeys = new HashSet<TKey>();
            return source.Where(element => seenKeys.Add(keySelector(element)));
        }

        /// <summary>
        /// Get Element at Indexed position, if no element exists then get a default value as passed in by paramater.
        /// </summary>
        /// <typeparam name="TSource">The type of the source.</typeparam>
        /// <param name="source">The source.</param>
        /// <param name="index">The index.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static TSource ElementAtOrDefault<TSource>(this IEnumerable<TSource> source, int index, TSource defaultValue)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (index >= 0)
            {
                var list = source as IList<TSource>;
                
                if (list != null)
                {
                    if (index < list.Count)
                    {
                        return list[index];
                    }
                }
                else
                {
                    using (var enumerator = source.GetEnumerator())
                    {
                        while (enumerator.MoveNext())
                        {
                            if (index == 0)
                            {
                                return enumerator.Current;
                            }
                            index--;
                        }
                    }
                }
            }
            return defaultValue;
        }

        public static IEnumerable<NavigationItem<T>> AsNavigable<T>(this IEnumerable<T> collection)
        {
            if (collection == null)
            {
                throw new ArgumentNullException("collection");
            }

            return AsNavigableCore(collection);
        }

        public static IEnumerable<NavigationItem<T>> AsNavigable<T>(this IQueryable<T> queryable)
        {
            if (queryable == null)
            {
                throw new ArgumentNullException("queryable");
            }

            return AsNavigableCore(queryable.AsEnumerable());
        }

        private static IEnumerable<NavigationItem<T>> AsNavigableCore<T>(IEnumerable<T> collection)
        {
            var index = 0;
            
            using (var lEnumerator = collection.GetEnumerator())
            {
                if (lEnumerator.MoveNext())
                {
                    var lCurrent = lEnumerator.Current;
                    bool lIsFirst = true, lIsEven = true;

                    while (lEnumerator.MoveNext())
                    {
                        yield return new NavigationItem<T>(lCurrent, index++, lIsFirst, false, lIsEven);
                        lIsFirst = false;
                        lIsEven = !lIsEven;
                        lCurrent = lEnumerator.Current;
                    }

                    yield return new NavigationItem<T>(lCurrent, index++, lIsFirst, true, lIsEven);
                }
            }
        }

        public class NavigationItem<T>
        {
            readonly T _value;
            readonly int _index;
            readonly bool _isFirst;
            readonly bool _isLast;
            readonly bool _isEven;

            internal NavigationItem(T value, int index, bool isFirst, bool isLast, bool isEven)
            {
                _value = value;
                _index = index;
                _isEven = isEven;
                _isFirst = isFirst;
                _isLast = isLast;
            }

            public T Value { get { return _value; } }

            public int Index { get { return _index; } }

            public bool IsFirst { get { return _isFirst; } }
            public bool IsLast { get { return _isLast; } }

            public bool IsOuter { get { return IsFirst || IsLast; } }
            public bool IsInner { get { return !IsOuter; } }

            public bool IsEven { get { return _isEven; } }
            public bool IsOdd { get { return !_isEven; } }

        }
    }
}